/**
 * Copyright (c) 2012-2014, Yang Yang <cntototo@gmail.com>
 *
 * This software may be distributed under the terms of the New BSD License.
 * See README for more details.
 */

#include "func_generic.h"

#define LOG_TAG "FUNC-GENERIC"

#include "include.h"
#include "def.h"
#include "util.h"
#include "if_manager.h"
#include "log.h"
#include "driver.h"

#ifdef CONFIG_NL80211
extern struct wfs_driver_ops wfs_driver_nl80211_ops;
#endif

/***********************************************************
 *  Local variables
 ***********************************************************
 */
static struct wfs_driver_ops *drivers[] = {
#ifdef CONFIG_NL80211
	&wfs_driver_nl80211_ops,
#endif
	NULL
};


/***********************************************************
 *  Local functions
 ***********************************************************
 */
static enum wfs_internal_err
func_get_status(struct wfs_iface *cur_if, struct wfs_cmd *cmd, char **resp,
		int *resp_len, u32 max_len)
{
	enum wfs_internal_err ret = WFS_INTERNAL_ERR_SUCCESS;
	char *pos = NULL, *end = NULL;
	int res = 0;

	assert(cur_if && cmd && resp && *resp && resp_len && max_len);

	ENTER();

	pos = *resp;
	end = pos + max_len;

	res = snprintf(pos, end - pos,
		"ifname=%s\n"
		"",
		cur_if->ifname);
	if (res < 0 || res >= end - pos) {
		ret = WFS_INTERNAL_ERR_INVAL;
		goto out;
	}
	pos += res;

	if (!cur_if->driver_ops)
		goto out;

	res = snprintf(pos, end - pos,
		"driver=%s\n"
		"",
		cur_if->driver_ops->name);
	if (res < 0 || res >= end - pos) {
		ret = WFS_INTERNAL_ERR_INVAL;
		goto out;
	}
	pos += res;

	ret = wfs_driver_status(cur_if, (u8 *) pos, end - pos, (u32 *) &res);
	if (ret)
		goto out;
	pos += res;

out:
	*resp_len = pos - *resp;

	LEAVE();
	return ret;
}


static enum wfs_internal_err
select_driver(struct wfs_iface *cur_if, u32 idx)
{
	enum wfs_internal_err ret = WFS_INTERNAL_ERR_SUCCESS;
	u32 len = 0;

	assert(cur_if);

	ENTER();

	if (cur_if->driver_ops) {
		len = strlen(drivers[idx]->name);
		if (strncmp(cur_if->driver_ops->name, drivers[idx]->name, len)
				== 0) {
			wfs_printf(LOG_DEBUG, "Already using driver %s",
					cur_if->driver_ops->name);
			goto out;
		}
		wfs_printf(LOG_DEBUG, "De-initialize existing driver %s",
				cur_if->driver_ops->name);
		ret = wfs_driver_deinit(cur_if);
		if (ret)
			goto out;
	}

	cur_if->driver_ops = drivers[idx];
	wfs_printf(LOG_DEBUG, "Initialize new driver %s",
			cur_if->driver_ops->name);
	ret = wfs_driver_init(cur_if);

out:
	LEAVE();
	return ret;
}


static enum wfs_internal_err
func_use_driver(struct wfs_iface *cur_if, struct wfs_cmd *cmd, char **resp,
		int *resp_len, u32 max_len)
{
	enum wfs_internal_err ret = WFS_INTERNAL_ERR_INVAL;
	char *driver_name = NULL;
	char *pos = NULL, *end = NULL;
	u32 i = 0, len = 0;
	int res = 0;

	assert(cur_if && cmd && resp && *resp && resp_len && max_len);

	ENTER();

	pos = *resp;
	end = pos + max_len;

	if (cmd->param_num != 1 || !strstr(cmd->params[0], "driver=")) {
		res = snprintf(pos, end - pos, "Usage: %s", USG(USE_DRIVER));
		if (res < 0 || res >= end - pos) {
			ret = WFS_INTERNAL_ERR_OS;
			goto out;
		}
		pos += res;
		goto out;
	}
	if (ARRAY_SIZE(drivers) == 1) {
		res = snprintf(pos, end - pos, "No drivers enabled.");
		if (res < 0 || res >= end - pos) {
			ret = WFS_INTERNAL_ERR_OS;
			goto out;
		}
		pos += res;
		goto out;
	}

	driver_name = strchr(cmd->params[0], KEY_VALUE_DELIM) + 1;
	len = strlen(driver_name);
	/** Find the required driver in table */
	for (i = 0; drivers[i]; ++i) {
		if (strncmp(drivers[i]->name, driver_name, len) == 0) {
			ret = select_driver(cur_if, i);
			goto out;
		}
	}

	res = snprintf(pos, end - pos, "Unsupported driver %s", driver_name);
	if (res < 0 || res >= end - pos) {
		ret = WFS_INTERNAL_ERR_OS;
		goto out;
	}
	pos += res;

out:
	*resp_len = pos - *resp;

	LEAVE();
	return ret;
}


/***********************************************************
 *  Global functions
 ***********************************************************
 */
enum wfs_internal_err func_generic_command(struct wfs_cmd *cmd,
		struct wfs_iface *cur_if, char **resp, int *resp_len,
		u32 max_len)
{
	enum wfs_internal_err ret = WFS_INTERNAL_ERR_SUCCESS;

	assert(cmd && cur_if && resp && *resp && resp_len && max_len);

	ENTER();

	wfs_printf(LOG_DEBUG, "Receives command: %s", cmd->cmd);

	if (IS_CMD(cmd->cmd, STATUS))
		ret = func_get_status(cur_if, cmd, resp, resp_len, max_len);
	else if (IS_CMD(cmd->cmd, USE_DRIVER))
		ret = func_use_driver(cur_if, cmd, resp, resp_len, max_len);

	LEAVE();
	return ret;
}
